/*******************************************************************************
 * Venice Noise Android Application
 * Copyright (C) 2011  Worcester Polytechnic Institute, Wesley Ripley
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; version 2
 * of the License
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 * 
 * If you plan to use or modify any of this code please contact Fabio Carrera (carrera.fabio@gmail.com)
 * Also please attribute any code used in the final product to the developers. 
 * 
 * Author: Wesley Ripley (wripley@wpi.edu) 
 *******************************************************************************/
package org.venicenoise.app.location;

import org.venicenoise.app.StateSaveable;

import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.location.Location;
import android.location.LocationManager;

/**
 * A GeneralLocationService that can save its state to the SharedPreferences
 * @author Wesley Ripley
 * @version 12/15/2011
 *
 */
public class GeneralSaveableLocationService extends GeneralLocationService implements StateSaveable {
	/**
	 * Prefix for shared preferences keys to make sure keys are unique
	 */
	private final static String namePrefix = "SaveableLocationService_";
	
	private final String uniqueName;

	/**
	 * Constructor for GeneralSaveableLocationService. 
	 * For use to create a new GeneralSaveableLocationService, before location is ever saved.
	 * @param locationManager
	 * 		The location manager to request location updates from
	 * @param locationPicker
	 * 		The location picker defining how to choose one location over another
	 * @param id
	 * 		The unique id for this location service. Used to store preferences uniquely
	 */
	public GeneralSaveableLocationService(LocationManager locationManager, LocationPicker locationPicker, int id)
	{
		super(locationManager,locationPicker);
		uniqueName = namePrefix+id;
	}
	
	/**
	 * Constructor for GeneralSaveableLocationService. 
	 * For use to load an already saved GeneralSaveableLocationService
	 * @param locationManager
	 * 		The location manager to request location updates from
	 * @param locationPicker
	 * 		The location picker defining how to choose one location over another
	 * @param id
	 * 		The unique id for this location service. Used to store preferences uniquely
	 * @param prefs
	 * 		The preferences where the GeneralSaveableLocationService was previously saved
	 */
	public GeneralSaveableLocationService(LocationManager locationManager, LocationPicker locationPicker, int id, SharedPreferences prefs)
	{
		this(locationManager,locationPicker,id);
		loadState(prefs);
	}
	
	//keys for storing location state (top-level) information
	private static final String KEY_LOCATING = "_islocating";
	private static final String KEY_LOCATION_AVAILABLE = "_locationAvailable";
	private static final String LOCATION_NAME = "_location";
	
	public Editor saveSate(Editor prefsEditor) {
		prefsEditor.putBoolean(getSharedPrefsKey(KEY_LOCATION_AVAILABLE), locationAvailable());
		prefsEditor.putBoolean(getSharedPrefsKey(KEY_LOCATING), isLocating());
		Location locationToSave = locationAvailable() ? getLocation() : null;
		prefsEditor = saveLocation(prefsEditor,locationToSave,LOCATION_NAME);
		return prefsEditor;
	}

	public boolean loadState(SharedPreferences prefs) {		
		
		boolean locationAvailable = prefs.getBoolean(getSharedPrefsKey(KEY_LOCATION_AVAILABLE), false);
		if(locationAvailable)
		{
			Location savedLocation = loadLocation(prefs,LOCATION_NAME);
			if(savedLocation == null)
			{
				return false;
			}
			bestLocation = savedLocation;
		}
		setLocating(prefs.getBoolean(getSharedPrefsKey(KEY_LOCATING), false));
		return true;
	}
	
	//keys for storing specific location information
	private static final String KEY_LOC_PROVIDER = "_provider";
	private static final String KEY_LOC_LAT = "_latitude";
	private static final String KEY_LOC_LONG = "_longitude";
	private static final String KEY_LOC_ACCURACY = "_accuracy";
	private static final String KEY_LOC_BEARING = "_bearing";
	private static final String KEY_LOC_ALTITUDE = "_altitude";
	private static final String KEY_LOC_SPEED = "_speed";
	private static final String KEY_LOC_TIME = "_time";
	//an array of the keys above so it is easy to iterate through them
	private static final String[] KEY_LOCS = { KEY_LOC_PROVIDER, KEY_LOC_LAT, 
												KEY_LOC_LONG, KEY_LOC_ACCURACY, 
												KEY_LOC_BEARING,KEY_LOC_ALTITUDE, 
												KEY_LOC_SPEED, KEY_LOC_TIME };
	/**
	 * Load a location saved in shared preferences
	 * @param prefs
	 * 		the preferences where the location information is saved
	 * @param locationName
	 * 		the name of the saved location
	 * @return
	 * 		the location, or null if no location could be loaded
	 */
	private Location loadLocation(SharedPreferences prefs, String locationName)
	{
		String provider = prefs.getString(getLocationSharedPrefsKey(locationName,KEY_LOC_PROVIDER), "");
		if(provider.equals(""))
		{
			return null;
		}
		Location loc = new Location(provider);
		//get latitiude
		String latKey = getLocationSharedPrefsKey(locationName,KEY_LOC_LAT);
		if(!prefs.contains(latKey))
		{
			//can't make a location without a latitude
			return null;
		}
		loc.setLatitude(prefs.getFloat(latKey, 0));
		//get longitude
		String longKey = getLocationSharedPrefsKey(locationName,KEY_LOC_LONG);
		if(!prefs.contains(longKey))
		{
			//can't make a location without a longitude
			return null;
		}
		loc.setLongitude(prefs.getFloat(longKey, 0));
		//get time
		String timeKey = getLocationSharedPrefsKey(locationName,KEY_LOC_TIME);
		if(!prefs.contains(timeKey))
		{
			//can't make a location without a time
			return null;
		}
		loc.setTime(prefs.getLong(timeKey, 0));
		//get accuracy
		String accuracyKey = getLocationSharedPrefsKey(locationName,KEY_LOC_ACCURACY);
		if(prefs.contains(accuracyKey))
		{
			loc.setAccuracy(prefs.getFloat(accuracyKey, 0));
		}
		//get bearing
		String bearingKey = getLocationSharedPrefsKey(locationName,KEY_LOC_BEARING);
		if(prefs.contains(bearingKey))
		{
			loc.setBearing(prefs.getFloat(bearingKey, 0));
		}
		//get altitude
		String altitudeKey = getLocationSharedPrefsKey(locationName,KEY_LOC_ALTITUDE);
		if(prefs.contains(altitudeKey))
		{
			loc.setAltitude(prefs.getFloat(altitudeKey, 0));
		}
		//get speed
		String speedKey = getLocationSharedPrefsKey(locationName,KEY_LOC_SPEED);
		if(prefs.contains(speedKey))
		{
			loc.setSpeed(prefs.getFloat(speedKey, 0));
		}
		return loc;
	}
	
	/**
	 * Save a location to shared preferences. 
	 * Changes must be committed later for them to take effect!
	 * @param prefsEditor
	 * 		The editor to use to save the location
	 * @param location
	 * 		The location to save
	 * @param locationName
	 * 		The name to save the location under
	 * @return
	 * 		The editor after the location has been saved. 
	 * 		Allows this call to be chained with other edits to preferences
	 */
	private Editor saveLocation(Editor prefsEditor, Location location, String locationName) {
		if(prefsEditor == null || locationName == null)
		{
			//must have a name and a editor to write location
			//return editor as given as to not break chaining
			return prefsEditor;
		}
		if(location == null)
		{
			//no location so clear all entries for this location
			for(String key : KEY_LOCS)
			{
				prefsEditor.remove(key);
			}
			return prefsEditor;
		}
		//always write provider, latitude, longitude, and time
		prefsEditor.putString(getLocationSharedPrefsKey(locationName,KEY_LOC_PROVIDER), location.getProvider());
		prefsEditor.putFloat(getLocationSharedPrefsKey(locationName,KEY_LOC_LAT), (float)location.getLatitude());
		prefsEditor.putFloat(getLocationSharedPrefsKey(locationName,KEY_LOC_LONG), (float)location.getLongitude());
		prefsEditor.putLong(getLocationSharedPrefsKey(locationName,KEY_LOC_TIME),location.getTime());
		//conditionally write optional location fields, removing them if not specified in location
		updateConditionalKeyInPrefs(prefsEditor, getLocationSharedPrefsKey(locationName,KEY_LOC_ACCURACY),location.hasAccuracy(),location.getAccuracy());
		updateConditionalKeyInPrefs(prefsEditor, getLocationSharedPrefsKey(locationName,KEY_LOC_BEARING),location.hasBearing(),location.getBearing());
		updateConditionalKeyInPrefs(prefsEditor, getLocationSharedPrefsKey(locationName,KEY_LOC_ALTITUDE), location.hasAltitude(), (float)location.getAltitude());
		updateConditionalKeyInPrefs(prefsEditor, getLocationSharedPrefsKey(locationName,KEY_LOC_SPEED), location.hasSpeed(),location.getSpeed());
		return prefsEditor;
	}
	
	/**
	 * Get the unique key for a given top level field
	 * @param fieldName
	 * 		The field to get the key for
	 * @return
	 * 		The unique key for this specific location service
	 */
	private String getSharedPrefsKey(String fieldName)
	{
		return uniqueName + fieldName;
	}
	
	/**
	 * Get the unique key for a given location field
	 * @param locationName
	 * 		The name of the location using this key
	 * @param fieldName
	 * 		The field to get the key for
	 * @return
	 * 		The unique key for this specific location service
	 */
	private String getLocationSharedPrefsKey(String locationName, String fieldName)
	{
		return uniqueName + locationName + fieldName;
	}
	
	/**
	 * Conditionally updates a float value in SharedPreferences
	 * @param prefsEditor
	 * 		The editor to use to write the updates
	 * @param key
	 * 		The key to update
	 * @param stillExists
	 * 		true if key should be updated, false if it should be removed
	 * @param value
	 * 		The value to set. Ignored is stillExists is false.
	 */
	private void updateConditionalKeyInPrefs(Editor prefsEditor, String key, boolean stillExists, float value)
	{
		if(stillExists)
		{
			prefsEditor.putFloat(key, value);
		}
		else
		{
			prefsEditor.remove(key);
		}
	}
	/**
	 * Set the locating status
	 * @param isLocating
	 * 		Whether this location service is locating or not.
	 * 		true means it is, false if it is not
	 */
	private void setLocating(boolean isLocating)
	{
		if(isLocating)
		{
			startLocating();
		}
		else
		{
			stopLocating();
		}
	}

}
